from tuntun.celery import app
from apps.user.models import User
from apps.product.models import GoodsBlock
from django.db.models import Q
import time
from .dbviews import ViewGoodsBlockWithProductInfo
from django.db.models import Sum
from apps.user.models import UserTotalValue
import calendar
from utils import weekday_strs
from apps.order.models import *
from apps.message.models import *
from apps.product.models import *
from django.db import transaction
from sdks.getui import GetuiClient
import json
from tuntun.settings import ALIPAY
from sdks.alipay import AliPay
from apps.user.models import WithdrawCash
import time
from tuntun.settings import GIFT_EXPIRE_TIME
from apps.system.models import SystemLog
from apps.order.flows import OrderFlow

import celery
from django.db.models import Q


class MyBaseTask(celery.Task):
    def on_failure(self, exc, task_id, args, kwargs, einfo):
        print(exc, task_id, args, kwargs, einfo)

    def on_success(self, retval, task_id, args, kwargs):
        print(retval, task_id, args, kwargs)


@app.task(name="定时统计所有用户持仓总价值", base=MyBaseTask)
def task_statistics_user_tea_repo_total_value():
    """
    每小时统计一次持仓总价值，存在总价值表，
    然后生成日/周/月图表数据更新用户表字段，
    用户获取曲线图直接从用户表取
    """
    import datetime
    now = datetime.datetime.now()
    today = now.date()
    weekday = now.weekday()
    y = now.year
    m = now.month
    d = now.day
    _, month_days = calendar.monthrange(y, m)
    today_timestamp_range = (
        datetime.datetime(y, m, d, 0, 0, 0).timestamp(),
        datetime.datetime(y, m, d, 23, 59, 59).timestamp())
    week_start_date, week_end_date = today - datetime.timedelta(days=weekday), today + datetime.timedelta(
        days=7 - weekday)
    current_week_timestamp_range = (
        datetime.datetime(week_start_date.year, week_start_date.month, week_start_date.day, 0, 0, 0).timestamp(),
        datetime.datetime(week_end_date.year, week_end_date.month, week_end_date.day, 23, 59, 59).timestamp()
    )
    current_month_timestamp_range = (
        datetime.datetime(y, m, 1, 0, 0, 0).timestamp(),
        datetime.datetime(y, m, month_days, 23, 59, 59).timestamp()
    )
    user_total_values = ViewGoodsBlockWithProductInfo.objects.values('owner_user_id').annotate(
        total_value=Sum('avg_price'))
    for obj in user_total_values:
        user_id, total_value = obj['owner_user_id'], obj['total_value']
        user = User.objects.filter(pk=user_id).first()
        if not user:
            continue
        utv = UserTotalValue.objects.create(user=user, total_value=total_value,
                                            statistics_datetime=datetime.datetime.now())

        data = UserTotalValue.get_line_data_for_user(user, True)
        today_data = data['today_data']
        week_data = data['week_data']
        month_data = data['month_data']
        user.today_total_value_data = today_data
        user.current_week_total_value_data = week_data
        user.current_month_total_value_data = month_data
        user.save()

    return {"count": user_total_values.count(), 'memo': 'Statistics user tea repo total value'}


@app.task(name='定时统计用户持仓总价值日曲线数据')
def crontab_statistics_user_day_total_value():
    from apps.user.models import User, UserKeepValue
    from apps.mix.dbviews import ViewUserProduct
    from apps.product.models import ProductLinePointPrice, Product
    us = User.objects.all()
    for u in us:
        keep_objs = ViewUserProduct.objects.filter(owner_user_id=u.id)
        point_data = []
        point_total_value = Decimal('0.00')
        for ko in keep_objs:
            product = Product.objects.get(pk=ko.product_id)
            avg_price = ProductLinePointPrice.get_product_avg_price_by_sub_orders(product, 'T')
            product_total_value = Decimal(str(ko.user_keep_count)) * Decimal(str(avg_price))
            point_data.append({
                'product_id': ko.product_id,
                'user_keep_count':  ko.user_keep_count,
                'product_avg_price': product.avg_price_custom.__str__(),
                'product_total_value': product_total_value.__str__(),
            })
            point_total_value += product_total_value
        if not UserKeepValue.objects.filter(user=u, datetime_point=UserKeepValue.get_current_datetime_point(kind='T'), kind='T'):
            UserKeepValue.objects.create(user=u,
                                         datetime_point=UserKeepValue.get_current_datetime_point(kind='T'),
                                         kind='T',
                                         keep_info=point_data,
                                         total_value=point_total_value)
        UserKeepValue.update_user_data(user=u, kind='T')
    return us.count()


@app.task(name='定时统计用户持仓总价值周曲线数据')
def crontab_statistics_user_week_total_value():
    from apps.user.models import User, UserKeepValue
    from apps.mix.dbviews import ViewUserProduct
    from apps.product.models import ProductLinePointPrice, Product
    us = User.objects.all()
    for u in us:
        keep_objs = ViewUserProduct.objects.filter(owner_user_id=u.id)
        point_data = []
        point_total_value = Decimal('0.00')
        for ko in keep_objs:
            product = Product.objects.get(pk=ko.product_id)
            avg_price = ProductLinePointPrice.get_product_avg_price_by_sub_orders(product, 'W')
            product_total_value = Decimal(str(ko.user_keep_count)) * Decimal(str(avg_price))
            point_data.append({
                'product_id': ko.product_id,
                'user_keep_count':  ko.user_keep_count,
                'product_avg_price': product.avg_price_custom.__str__(),
                'product_total_value': product_total_value.__str__(),
            })
            point_total_value += product_total_value
        if not UserKeepValue.objects.filter(user=u, datetime_point=UserKeepValue.get_current_datetime_point(kind='W'), kind='W'):
            UserKeepValue.objects.create(user=u,
                                         datetime_point=UserKeepValue.get_current_datetime_point(kind='W'),
                                         kind='W',
                                         keep_info=point_data,
                                         total_value=point_total_value)
        UserKeepValue.update_user_data(user=u, kind='W')
    return us.count()


@app.task(name='定时统计用户持仓总价值月曲线数据')
def crontab_statistics_user_month_total_value():
    from apps.user.models import User, UserKeepValue
    from apps.mix.dbviews import ViewUserProduct
    from apps.product.models import ProductLinePointPrice, Product
    us = User.objects.all()
    for u in us:
        keep_objs = ViewUserProduct.objects.filter(owner_user_id=u.id)
        point_data = []
        point_total_value = Decimal('0.00')
        for ko in keep_objs:
            product = Product.objects.get(pk=ko.product_id)
            avg_price = ProductLinePointPrice.get_product_avg_price_by_sub_orders(product, 'M')
            product_total_value = Decimal(str(ko.user_keep_count)) * Decimal(str(avg_price))
            point_data.append({
                'product_id': ko.product_id,
                'user_keep_count':  ko.user_keep_count,
                'product_avg_price': avg_price.__str__(),
                'product_total_value': product_total_value.__str__(),
            })
            point_total_value += product_total_value
        if not UserKeepValue.objects.filter(user=u, datetime_point=UserKeepValue.get_current_datetime_point(kind='M'), kind='M'):
            UserKeepValue.objects.create(user=u,
                                         datetime_point=UserKeepValue.get_current_datetime_point(kind='M'),
                                         kind='M',
                                         keep_info=point_data,
                                         total_value=point_total_value)

        UserKeepValue.update_user_data(user=u, kind='M')
    return us.count()


@app.task(name="定时清理预售下单前锁定的区块的锁定状态")
def task_clean_pre_sale_before_order_locked_blocks():
    """Task"""
    now_timestamp = time.time()
    gb_count = 0
    gbs = GoodsBlock.objects.filter(Q(locked_expire_at__lt=now_timestamp - 5) & Q(locked_expire_at__gt=0))
    for gb in gbs:
        if not SubOrder.objects.filter(goods_block_id=gb.id):  # 只处理预售下单前
            gb.update(locked_by_user_id=None, locked_expire_at=0)
            gb_count += 1
    res = {"count": gbs.count(), 'gb_count': gb_count}
    SystemLog.info(text_log=None, json_log=res)


@app.task(name="有人发布挂单时定时或延时匹配")
def task_match_market_order(new_market_order_id):
    """Task"""
    res = {'new_market_order_id': new_market_order_id, 'matched_market_orders': []}
    new_market_order = MarketOrder.objects.get(pk=new_market_order_id)

    if new_market_order.business_type == MarketOrder.BusinessType.MARKET_TRADE_SELL.name:  # 欲卖
        # 匹配买入挂单
        matched_market_orders = MarketOrder.objects.filter(
            ~Q(user_id=new_market_order.user_id),  # 排除发布者
            product=new_market_order.product,  # 匹配产品
            intention_type=MarketOrder.IntentionType.BUY.name,  # 匹配意向
            status=MarketOrder.Status.ING.name,  # 匹配状态
            count__lte=new_market_order.count,  # 匹配数量
            publish_type=MarketOrder.PublishType.MARKET.name,  # 匹配发布类型
            offered_price__lt=new_market_order.offered_price + Decimal('10.00'),  # 匹配价格
            offered_price__gt=new_market_order.offered_price - Decimal('10.00'),  # 匹配价格
        ).order_by('count', 'offered_price')
    elif new_market_order.business_type == MarketOrder.BusinessType.MARKET_TRADE_BUY.name:  # 求购
        matched_market_orders = MarketOrder.objects.filter(
            ~Q(user_id=new_market_order.user_id),  # 排除发布者
            product=new_market_order.product,  # 匹配产品
            intention_type=MarketOrder.IntentionType.SELL.name,  # 匹配意向
            status=MarketOrder.Status.ING.name,  # 匹配状态
            count__gte=new_market_order.count,  # 匹配数量
            publish_type=MarketOrder.PublishType.MARKET.name,  # 匹配发布类型
            offered_price__lt=new_market_order.offered_price + Decimal('10.00'),  # 匹配价格
            offered_price__gt=new_market_order.offered_price - Decimal('10.00'),  # 匹配价格
        ).order_by('count', 'offered_price')
    else:
        matched_market_orders = []

    # 发消息
    for matched_market_order in matched_market_orders:
        if new_market_order.business_type == MarketOrder.BusinessType.MARKET_TRADE_SELL.name:
            message_market_order = new_market_order
            message_to_user = User.objects.get(pk=matched_market_order.user_id)
        else:
            message_market_order = matched_market_order
            message_to_user = User.objects.get(pk=new_market_order.user_id)
        message = Message(
            from_user_id='',
            title='囤囤为你匹配最优买入方案',
            content='%s你有可选择购买方案...' % new_market_order.product.name,
            msg_type=Message.MsgType.WANT_BUY.name,
            market_order_id=message_market_order.pk,
        )
        message.save()
        res['matched_market_orders'].append({'moid': matched_market_order.id, 'msgid': message.id})
        uum, _ = UserUnreadMessage.objects.get_or_create(user_id=message_to_user.pk,)
        uum.unread_message_ids[Message.MsgType.WANT_BUY.name].append(message.pk)
        uum.last_message_id[Message.MsgType.WANT_BUY.name] = message.pk
        uum.save()
        # 透传消息
        transmission_msg_data = message.make_transmission_msg()
        pr = PushRecord.objects.create(
            user_id=message_to_user.pk,
            message=message,
            content=json.dumps(transmission_msg_data),
            send_type=PushRecord.SendType.TRANSMISSION.name,
            provider=PushRecord.Provider.GETUI.name,
            success=False,
            provider_response_status_code=0,
            provider_response_data=None,
        )
        # 发送透传
        getui = GetuiClient()
        status_code, rs = getui.push_single_transmission(
            cid=message_to_user.getui_clientid,
            transmission_content=transmission_msg_data,
        )
        pr.provider_response_status_code = status_code
        pr.provider_response_data = rs
        if status_code == 200 and rs['result'] == 'ok':
            pr.success = True
            pr.provider_response_data = json.dumps(rs)
        else:
            pr.provider_response_data = rs
            pr.provider_response_status_code = status_code
            pr.success = False
        pr.save()
    return res


@app.task(name='发送透传消息')
def task_send_transmission_msg(user_id, transmission_msg_data, message_id=None,
                               provider=PushRecord.Provider.GETUI.name, with_push=False, title='', body=''):
    if message_id:
        message = Message.objects.get(pk=message_id)
    else:
        message = None
    if message:
        title = message.title
        body = message.content
        notification_msg_data = message.make_notification_msg()
    else:
        title = title
        body = body
        notification_msg_data = {'title': title, 'body': body}
    #user = User.objects.filter(pk=user_id).first()
    user_devices = UserDevice.objects.filter(user_id=user_id)
    for user_device in user_devices:
        transmission_msg_data = transmission_msg_data
        if provider == PushRecord.Provider.GETUI.name:
            print('-----%s-----' % provider)
            # 透传消息
            pr = PushRecord.objects.create(
                user_id=user_device.user_id, #user.pk,
                message=message,
                content=json.dumps(transmission_msg_data),
                send_type=PushRecord.SendType.TRANSMISSION.name,
                provider=PushRecord.Provider.GETUI.name,
                success=False,
                provider_response_status_code=0,
                provider_response_data=None,
            )
            # 发送透传
            getui = GetuiClient()
            status_code, rs = getui.push_single_transmission(
                cid=user_device.getui_clientid, #user.getui_clientid,
                transmission_content=transmission_msg_data,
            )
            pr.provider_response_status_code = status_code
            pr.provider_response_data = rs
            if status_code == 200 and rs['result'] == 'ok':
                pr.success = True
                pr.provider_response_data = json.dumps(rs)
            pr.save()
            # 推送
            if with_push:
                prp = PushRecord.objects.create(
                    user_id=user_device.user_id, #user.pk,
                    message=message,
                    content=json.dumps(notification_msg_data),
                    send_type=PushRecord.SendType.NOTIFY.name,
                    provider=PushRecord.Provider.GETUI.name,
                    success=False,
                    provider_response_status_code=0,
                    provider_response_data=None,
                )
                # 发送
                status_code, rs = getui.push_list_notification_one_step([user_device.getui_clientid,], notification_msg_data, True, title, body)
                pr.provider_response_status_code = status_code
                pr.provider_response_data = rs
                if status_code == 200 and rs['result'] == 'ok':
                    pr.success = True
                    pr.provider_response_data = json.dumps(rs)
                pr.save()
        else:
            pass


@app.task(name='提现（转账到支付宝账户）')
def task_withdraw_to_alipay_account(withdraw_cash_id):
    withdraw_cash = WithdrawCash.objects.get(pk=withdraw_cash_id)
    res = {'withdraw_cash_id': withdraw_cash.pk}
    alipay = AliPay(
        appid=ALIPAY['APPID'].strip(),
        app_notify_url=ALIPAY['NOTIFY_URL'],
        app_private_key_string=ALIPAY['APP_PRIVATE_KEY'].strip(),
        alipay_public_key_string=ALIPAY['ALIPAY_PUBLIC_KEY'].strip(),
        sign_type='RSA2',
        debug=False,
    )

    to_account = withdraw_cash.to_account
    amount = withdraw_cash.amount

    # transfer money to alipay account
    result = alipay.api_alipay_fund_trans_toaccount_transfer(
        datetime.now().strftime("%Y%m%d%H%M%S"),
        payee_type="ALIPAY_LOGONID",  # ALIPAY_USERID
        payee_account=to_account,
        amount=float(amount)
    )

    code = result.get('code', None)
    with transaction.atomic():
        if code is not None and int(code) == 10000:
            withdraw_cash.state = WithdrawCash.State.FINISHED.name
            withdraw_cash.provider_order_id = result.get('order_id')
            withdraw_cash.provider_process_datetime = result.get('pay_date')
            withdraw_cash.provider_response_data = json.dumps(result)
            res['withdraw_cash_success'] = True
            # 余额变动
            user = User.objects.get(pk=withdraw_cash.user_id)
            UserBalanceRecord.objects.create(user=user,
                                             create_type=UserBalanceRecord.CreateType.WITHDRAW_CASH.name,
                                             amount=Decimal('-' + withdraw_cash.amount.__str__()), )
            user.balance -= withdraw_cash.amount
            user.save()
            withdraw_cash.save()
            return True
        else:
            withdraw_cash.state = WithdrawCash.State.FAILED.name
            withdraw_cash.provider_response_data = result
            res['withdraw_cash_success'] = False
            res['withdraw_cash_response'] = json.dumps(result)
            withdraw_cash.save()
            return False
    return False


@app.task(name='定时清理超时未支付订单')
def task_clean_timeout_unpay_order():
    import time, datetime
    now_timestamp = time.time()
    orders = Order.objects.filter(status=Order.Status.CREATED.name, expire_at__lt=now_timestamp - 5)
    with transaction.atomic():
        for order in orders:
            payment = Payment.objects.filter(order=order).order_by('-add_timestamp').first()
            if payment and payment.status == Payment.Status.PAYED.name:
                order.status = Order.Status.PAYED.name
                order.save()
                continue
            sub_orders = SubOrder.objects.filter(order=order)
            gb_ids = [so.goods_block_id for so in sub_orders]
            market_order_ids = [sub_order.market_order_id for sub_order in sub_orders]
            market_order = MarketOrder.objects.filter(id__in=market_order_ids).first()
            if market_order and market_order.intention_type == MarketOrder.IntentionType.SELL.name:
                if market_order.as_gift:
                    GoodsBlock.objects.filter(market_order_id__in=market_order_ids).update(market_order=None)
                    market_order.soft_delete()
                else:
                    market_order.status = MarketOrder.Status.ING.name
                    market_order.buy_in_market_order = None
                    market_order.save()
            elif market_order and market_order.intention_type == MarketOrder.IntentionType.TAKE_OUT.name:
                GoodsBlock.objects.filter(market_order_id__in=market_order_ids).update(market_order=None, trade_type=GoodsBlock.TradeType.MARKET.name)
                market_order.buy_in_market_order = None
                market_order.soft_delete()
            if not market_order:  # 预售下单未支付
                GoodsBlock.objects.filter(id__in=gb_ids).update(locked_by_user_id=None, locked_expire_at=0)
            import time
            SubOrder.objects.filter(order=order).update(deleted_at=time.time())
            order.soft_delete()

    return {'count': orders.count(), 'memo': '超时交易订单'}


@app.task(name='定时检查赠送超时订单')
def task_clean_timeout_gift_unreceive_order():
    res = {}
    import time, datetime
    now_timestamp = time.time()
    orders = Order.objects.filter(as_gift=True, as_gift_multiple=False, status=Order.Status.PAYED.name,
                                  update_timestamp__lt=now_timestamp - GIFT_EXPIRE_TIME)
    with transaction.atomic():
        for order in orders:
            res[order.pk] = {}
            sub_orders = SubOrder.objects.filter(order=order)
            first_sub_order = sub_orders.first()
            market_order_ids = [sub_order.market_order_id for sub_order in sub_orders]
            GoodsBlock.objects.filter(market_order_id__in=market_order_ids).update(market_order=None)
            order.status = Order.Status.EXPIRED.name
            order.save()
            # 退费到余额
            user = User.objects.get(pk=first_sub_order.seller_user_id)
            UserBalanceRecord.objects.create(user=user,
                                             create_type=UserBalanceRecord.CreateType.GIFT_REPO_COST_REFUND.name,
                                             amount=order.amount, order_id=order.id)
            user.balance += order.amount
            user.save()
    return {'count': orders.count(), 'memo': 'Gift Order UnReceive Order'}


@app.task(name='延时任务-预售发布')
def task_pre_sale_publish(product_id):
    res = {'effect': 0, 'memo': 'Publish Pre Sale'}
    import time
    product = Product.objects.get(pk=product_id)
    gbs = GoodsBlock.objects.filter(product_id=product_id)
    gbcs = []
    start_time = time.strftime('%H:%M:%S')

    for gb in gbs:
        if GoodsBlockChain.objects.filter(goods_block=gb, chain_type=GoodsBlockChain.ChainType.PRE_SALE.name):
            return res
        # 预售节点
        gbc = GoodsBlockChain(
            goods_block=gb,
            operator_user_id=None,
            chain_type=GoodsBlockChain.ChainType.PRE_SALE.name,
            node_datetime=datetime.now(),
            storage_repo_id=gb.repo_id,
            storage_repo_days=0
        )
        gbc_op = gbc.get_block_chain_node_outputs()
        gbc.title = gbc_op['title']
        gbc.contents = gbc_op['contents']
        gbc.repo_info = gbc_op['repo_info']
        gbc.trade_price = product.last_trade_price
        gbcs.append(gbc)
    with transaction.atomic():
        GoodsBlockChain.objects.bulk_create(gbcs)
        # 产品预售消息
        product_pre_sale_start_end = '%s-%s' % (
        product.pre_sale_start_time.strftime('%Y.%m.%d'), product.pre_sale_end_time.strftime('%m.%d'))
        msg = Message.objects.create(
            title="产品预售",
            content="%s开启%s预售，预售时间为：%s，敬请关注！" % (product.brand.name, product.name, product_pre_sale_start_end),
            msg_type=Message.MsgType.PRE_SALE.name,
        )
        cids = []
        for u in User.objects.all():
            uum, created = UserUnreadMessage.objects.get_or_create(user_id=u.pk)
            if created:
                uum.unread_message_ids = {e[0]: [] for e in Message.MsgType.get_choices()}
                uum.last_message_id = {e[0]: '' for e in Message.MsgType.get_choices()}
                uum.save()
            # 检查字段
            if Message.MsgType.PRE_SALE.name not in uum.unread_message_ids:
                uum.unread_message_ids[Message.MsgType.PRE_SALE.name] = []
            if Message.MsgType.PRE_SALE.name not in uum.last_message_id:
                uum.last_message_id[Message.MsgType.PRE_SALE.name] = ''
            uum.unread_message_ids[Message.MsgType.PRE_SALE.name].append(msg.pk)
            uum.last_message_id[Message.MsgType.PRE_SALE.name] = msg.pk
            uum.save()
            if u.getui_clientid:
                cids.append(u.getui_clientid)

        getui = GetuiClient()
        status_code, rs = getui.push_list_transmission_save_list_body(
            transmission_content=msg.make_transmission_msg(),
            transmission_type=True,
        )
        if type(rs) == dict and rs['result'] == 'ok':
            taskid = rs['taskid']
            getui.push_list_transmission(cids, taskid, need_detail=False)

        getui.push_list_notification_one_step(cids, msg.make_notification_msg(), True, msg.title, msg.content)

    end_time = time.strftime('%H:%M:%S')
    res.update({
        'start_time': start_time,
        'end_time': end_time,
    })
    res['effect'] = len(gbcs)
    return res


@app.task(name='定时检查挂单价值是否不足以抵扣存仓费')
def task_check_market_order_worth_and_repo_cost():
    res = {'effect': 0, 'memo': 'Check Market Order Worth and Repo Cost'}
    market_orders = MarketOrder.objects.filter(
        intention_type=MarketOrder.IntentionType.SELL.name,
        publish_type__in=[MarketOrder.PublishType.MARKET.name, MarketOrder.PublishType.DIRECTIONAL.name],
        as_gift=False,
    )
    for market_order in market_orders:
        worth = market_order.offered_price * Decimal(str(market_order.count))
        repo_amount = sum([e.repo_amount for e in GoodsBlock.objects.filter(market_order=market_order)])
        if worth < repo_amount:
            GoodsBlock.objects.filter(market_order=market_order).update(market_order=None)
            market_order.status = MarketOrder.Status.CANCELED.name
            market_order.save()
            res['effect'] += 1
    SystemLog.info(text_log=None, json_log=res)
    return res


@app.task(name='定时检查挂单状态，修复已结束的挂单')
def task_check_market_order_end_status():
    res = {'effect': 0, 'memo': 'Check Market Order Status'}
    market_orders = MarketOrder.objects.filter(
        status__in=[MarketOrder.Status.END.name, MarketOrder.Status.CANCELED.name])
    for market_order in market_orders:
        GoodsBlock.objects.filter(market_order=market_order).update(market_order=None)
    res['effect'] = len(market_orders)
    SystemLog.info(text_log=None, json_log=res)
    return res


@app.task(name='定时修复已支付未完成的挂单')
def task_fix_trading_market_order():
    import datetime, time
    res = {'effect': 0, 'memo': 'Check Pre Sale Product Time', 'ids': []}
    market_orders = MarketOrder.objects.filter(status=MarketOrder.Status.TRADE.name, as_gift_multiple=False)
    for market_order in market_orders:
        order = market_order.related_order
        if order and order.status == Order.Status.PAYED.name:
            market_order.status = MarketOrder.Status.END.name
            market_order.save()
    return res


@app.task(name='定时检查预售时间判定预售状态处理预售订单')
def task_check_pre_sale_status(product_id=None):
    from apps.product.views import CreateMessageView
    import datetime, time
    res = {'effect': 0, 'memo': 'Check Pre Sale Status', 'ids': []}
    n = 0
    ids = []
    if product_id:
        products = Product.objects.filter(id=product_id)
    else:
        products = Product.objects.filter(status__in=[Product.ProductStatus.PRE_SALE_ING.name, Product.ProductStatus.REPO_IN_ING.name])
    for p in products:
        sos = SubOrder.objects.filter(product_id=p.id, create_type=SubOrder.CreateType.PRE_SALE_BUY_IN.name,
                                      order__status=Order.Status.FINISHED.name)
        orders = Order.objects.filter(status=Order.Status.FINISHED.name, post_finished_done=False,
                                      id__in=[so.order_id for so in sos])
        if sos.count() >= p.publish_count:  # 预售成功
            # 后处理订单
            with transaction.atomic():
                for order in orders:
                    import time
                    print(time.strftime('%H:%M:%S'),order.pk)
                    first_sub_order = SubOrder.objects.filter(order=order).first()
                    if first_sub_order and first_sub_order.create_type == SubOrder.CreateType.PRE_SALE_BUY_IN.name:
                        of = OrderFlow(order)
                        of.post_trade_order_finished()
                        n += 1
                        ids.append(order.id)
                        # 发送消息
                        # 预售购买成功消息
                        msg = CreateMessageView.message(msg_type=Message.MsgType.PRE_SALE.name,
                                                        oper_user=None,
                                                        message_user_id=order.buyer_user.id,
                                                        product_name=first_sub_order.product_name,
                                                        order_id=order.id,
                                                        market_order_id=None,
                                                        title="预售成功：亲爱的囤囤用户，您购买的%s预售成功，茶饼即将在七个工作日内入库，请关注“我的藏茶”信息变化。" % first_sub_order.product_name,
                                                        content='预售成功：亲爱的囤囤用户，您购买的%s预售成功，茶饼即将在七个工作日内入库，请关注“我的藏茶”信息变化。' % first_sub_order.product_name)

                p.status = Product.ProductStatus.PRE_SALE_END.name
                p.save()
        else:
            if p.status == Product.ProductStatus.REPO_IN_ING.name:
                continue
            if p.pre_sale_end_time > datetime.datetime.now():
                continue
            # 预售失败
            with transaction.atomic():
                # 退款
                for order in orders:
                    buyer_user = User.objects.get(id=order.buyer_user_id)
                    # 预售购买退款消息
                    first_sub_order = SubOrder.objects.filter(order=order).first()
                    msg = CreateMessageView.message(msg_type=Message.MsgType.PRE_SALE.name,
                                                    oper_user=None,
                                                    message_user_id=order.buyer_user.id,
                                                    product_name=first_sub_order.product_name,
                                                    order_id=order.id,
                                                    market_order_id=None,
                                                    title="预售失败:	   亲爱的囤囤用户，您购买的%s预售失败，付款金额将在3日内退回您的囤囤账户。" % first_sub_order.product_name,
                                                    content='预售失败:	   亲爱的囤囤用户，您购买的%s预售失败，付款金额将在3日内退回您的囤囤账户。' % first_sub_order.product_name)
                    UserBalanceRecord.objects.create(user=buyer_user,
                                                     create_type=UserBalanceRecord.CreateType.PRE_SALE_FAILED_REFUND.name,
                                                     amount=order.amount, order_id=order.id)
                    buyer_user.balance += order.amount
                    buyer_user.save()
                    n += 1
                    ids.append(order.id)
                    # 预售购买退款成功消息
                    first_sub_order = SubOrder.objects.filter(order=order).first()
                    msg = CreateMessageView.message(msg_type=Message.MsgType.PRE_SALE.name,
                                                    oper_user=None,
                                                    message_user_id=order.buyer_user.id,
                                                    product_name=first_sub_order.product_name,
                                                    order_id=order.id,
                                                    market_order_id=None,
                                                    title="退款成功：亲爱的囤囤用户，您购买的「%s」，付款金额%s元已成功退回至您的囤囤账户，请在「钱包余额」中查看。" % (first_sub_order.product_name, order.amount.__str__()),
                                                    content='退款成功：亲爱的囤囤用户，您购买的「%s」，付款金额%s元已成功退回至您的囤囤账户，请在「钱包余额」中查看。' % (first_sub_order.product_name, order.amount.__str__()))

                # 处理产品状态
                p.status = Product.ProductStatus.PRE_SALE_FAILED.name
                p.save()
                gbs = GoodsBlock.objects.filter(product_id=p)
                gbs.update(owner_user_id=None, locked_by_user_id=None, locked_expire_at=0, sold=False)
                gbcs_to_deleted = GoodsBlockChain.objects.filter(goods_block__in=gbs, chain_type__in=[
                    GoodsBlockChain.ChainType.PRE_SALE.name, GoodsBlockChain.ChainType.PRE_SALE_BUY_IN.name])
                sos_to_deleted = SubOrder.objects.filter(order__in=orders)
                orders_to_deleted = orders
                GoodsBlockChain.batch_soft_delete(gbcs_to_deleted)
                SubOrder.batch_soft_delete(sos_to_deleted)
                Order.batch_soft_delete(orders_to_deleted)
    res['effect'] = n
    res['ids'] = ids
    return res


@app.task(name='定时统计产品曲线数据')
def crontab_make_product_line_data():
    from apps.mix.dbviews import ViewUserProduct
    from apps.product.models import Product, ProductLinePointPrice
    ps = Product.objects.all()
    for p in ps:
        line_data = ProductLinePointPrice.get_line_data_for_product(product=p)
        today_data = line_data['today_data']
        week_data = line_data['week_data']
        month_data = line_data['month_data']
        p.today_price_data = today_data
        p.week_price_data = week_data
        p.month_price_data = month_data
        p.save()
    return ps.count()


@app.task(name='异步为产品占位存储位置')
def async_lock_store_unit(product_id):
    import time
    from apps.product.models import Product
    from apps.repo.models import Repo, StoreUnitThree
    import time, datetime
    from tuntun.settings import HOST
    from apps.product.models import Product
    from apps.repo.models import Repo, StoreUnitThree
    from urllib.parse import parse_qs, urlparse
    print(time.time())
    product = Product.objects.get(pk=product_id)
    repo = Repo.objects.first()

    old_gbs = GoodsBlock.objects.filter(product_id=product_id)
    old_gbs_ids = [e[0] for e in old_gbs.values_list('id','store_unit_id')]
    old_sus_ids = [e[1] for e in old_gbs.values_list('id','store_unit_id')]
    old_gbcs = GoodsBlockChain.objects.filter(goods_block_id__in=old_gbs_ids)

    StoreUnitThree.objects.filter(id__in=old_sus_ids).update(in_use=False)
    old_gbcs.delete()
    old_gbs.delete()

    if GoodsBlock.objects.filter(product_id=product_id):
        return '已入库过'
    StoreUnitThree.objects.filter(locked_by_product_id=product_id).update(locked_by_product_id=None, locked_by_product_expire_at=0)

    exists_sus = []
    new_sus = []

    # 处理存储单元
    # 先找已存在的空闲
    store_spec = StoreUnitThree.StoreSpec.get_key_name_by_product_tea_cake_type(product.tea_cake_type)
    valid_repo_store_areas = StoreUnitThree.StoreSpec.get_valid_repo_store_areas(store_spec)
    valid_repo_indexs = StoreUnitThree.get_valid_repo_indexs()

    with transaction.atomic():
        exists_sus = StoreUnitThree.objects.filter(locked_by_product_id=None, locked_by_product_expire_at=0, in_use=False,
                                           store_spec=store_spec, repo=repo,
                                           repo_store_area__in=valid_repo_store_areas)
        exists_count = exists_sus.count()
        need_new_count = product.publish_count - exists_count
        if need_new_count <= 0:
            exists_sus.update(locked_by_product_id=product_id, locked_by_product_expire_at=time.time()+1800)
        else:
            new_sus = []
            print(time.time(), 'exists end')
            for valid_repo_index in valid_repo_indexs:
                for valid_repo_store_area in valid_repo_store_areas:
                    last_su = StoreUnitThree.objects.filter(
                        store_spec=store_spec, repo=repo,repo_index=valid_repo_index,
                        repo_store_area=valid_repo_store_area).order_by('-repo_no').first()
                    while True:
                        su_info = StoreUnitThree.get_next_store_unit_info(
                            last_su, valid_repo_index,valid_repo_store_area, repo.pk)
                        if not su_info:
                            break
                        su_info['locked_by_product_id'] = product_id
                        su_info['locked_by_product_expire_at'] = time.time() + 1800
                        su = StoreUnitThree(**su_info)
                        new_sus.append(su)
                        if len(new_sus) >= need_new_count:
                            break
                        last_su = su
                if len(new_sus) >= need_new_count:
                    break

            if len(new_sus) < need_new_count:
                raise Exception('存储空间不足')
        print(time.time(), 'news ready end')
        exists_sus.update(locked_by_product_id=product_id, locked_by_product_expire_at=time.time()+1800)
        print(time.time(), 'exists update end')
        StoreUnitThree.objects.bulk_create(new_sus)
        print(time.time(), 'news created end')

    task_start = time.time()
    print('start:', task_start)
    product = Product.objects.get(pk=product_id)
    repo = Repo.objects.first()

    if GoodsBlock.objects.filter(product=product).count() == product.publish_count:
        raise Exception('批量入库已完成')

    if product.status != Product.ProductStatus.REPO_IN_ING.name:
        raise Exception('产品必须在入库中状态才能入库')

    old_gbs = GoodsBlock.objects.filter(product_id=product_id)
    old_gbs_ids = [e[0] for e in old_gbs.values_list('id')]
    old_gbcs = GoodsBlockChain.objects.filter(goods_block_id__in=old_gbs_ids)

    StoreUnitThree.objects.filter(id__in=old_gbs_ids).update(in_use=False)
    old_gbcs.delete()
    old_gbs.delete()

    sus = StoreUnitThree.objects.filter(locked_by_product_id=product_id, in_use=False)
    sus.update(locked_by_product_expire_at=time.time()+1800)
    sus_datas = sus.values_list('repo_id', 'id')
    if sus.count() < product.publish_count:
        raise Exception('入库失败-仓库位置不足')

    size = 1000
    page = 1
    product_fields = product.get_unique_code()
    last_no_int_fields = None
    while True:
        # 匹配区块和仓储位置
        new_gbs = []
        new_gbcs = []
        su_ids = []
        #
        for i in range(size*(page-1), size*page):
            loop_start = time.time()
            last_no_int_fields = GoodsBlock.get_next_no_int_fields(last_no_int_fields)
            last_no_fields = [chr(last_no_int_fields[k]) if k in [0, 1] else int_2_2d(last_no_int_fields[k]) for k in
                              range(len(last_no_int_fields))]
            no = ''.join(last_no_fields)
            uc = ''.join(product_fields) + no

            su_data = sus_datas[i]
            su_ids.append(su_data[1])

            gb_data = {
                "product": product,
                "unique_code": uc,
                "unique_code_version": GoodsBlock.UniqueCodeVersion.DOG_YEAR_SPRING.name,
                "no": no,
                "fields": product_fields,
                'trade_type': GoodsBlock.TradeType.PRE_SALE.name,
                "repo_id": su_data[0],
                "store_unit_id": su_data[1],
                "trade_price": product.pre_sale_price,
                "avg_price": product.pre_sale_price,
                'trade_datetime': datetime.datetime.now(),
            }
            gb = GoodsBlock(**gb_data)
            new_gbs.append(gb)
            gbc = GoodsBlockChain(
                goods_block=gb,
                operator_user_id=None,
                chain_type=GoodsBlockChain.ChainType.IN_REPO.name,
                node_datetime=datetime.datetime.now(),
                storage_repo_id=su_data[0],
                storage_repo_days=0
            )
            gbc_op = gbc.get_block_chain_node_outputs()
            gbc.title = gbc_op['title']
            gbc.contents = gbc_op['contents']
            gbc.repo_info = gbc_op['repo_info']
            new_gbcs.append(gbc)
            loop_end = time.time()
            print(i, loop_end-loop_start, len(sus_datas))
            if i >= product.publish_count - 1:
                break
        GoodsBlock.objects.bulk_create(new_gbs)
        GoodsBlockChain.objects.bulk_create(new_gbcs)
        StoreUnitThree.objects.filter(id__in=su_ids).update(in_use=True)
        if i >= product.publish_count - 1:
            break
        del new_gbs
        del new_gbcs
        del su_ids
        page += 1

    sus.update(locked_by_product_expire_at=0, locked_by_product_id=None)
    task_end = time.time()
    return '共入库{d} 耗时：{t}'.format(d=product.publish_count, t=task_end-task_start)

@app.task(name='定时释放产品占位锁定的存储位置')
def crond_release_locked_store_unit():
    import time
    from django.db.models import Q
    from apps.repo.models import StoreUnitThree
    with transaction.atomic():
        StoreUnitThree.objects.filter(Q(locked_by_product_expire_at__lt=time.time())&Q(locked_by_product_expire_at__gt=0)).update(locked_by_product_expire_at=0, locked_by_product_id=None)


@app.task(name='异步分发')
def async_dispatch(product_id, user_infos):
    from tuntun.settings import REGEX_MOBILE
    import re
    import time
    import datetime
    import uuid
    from apps.repo.models import Repo
    from decimal import Decimal
    from apps.product.models import Product, GoodsBlock
    from apps.user.models import UserBalanceRecord
    from apps.order.models import SubOrder, Order
    from apps.repo.models import Repo
    from apps.product.views import CreateMessageView
    from apps.message.models import Message
    start = time.time()
    product = Product.objects.get(pk=product_id)
    all_gbs = GoodsBlock.objects.filter(product_id=product_id)
    mobile_count_map = {}
    with transaction.atomic():
        current_offset = 0
        for idx, ui in enumerate(user_infos.splitlines()):
            tmp = ui.strip()
            if not tmp: continue
            mobile, count = tmp.split(':')[0], tmp.split(':')[1]
            count = int(count)

            user = User.objects.filter(mobile=mobile).first()
            this_user_gbs = all_gbs[current_offset:current_offset + count]
            mobile_count_map[tmp.split(':')[0]] = {
                'user_id': user.id,
                'count': count,
                'user': user,
                'total_value': Decimal(str(count)) * product.pre_sale_price,
                'gbs': this_user_gbs,
                'gbs_count': len(this_user_gbs),
            }
            print(user, len(this_user_gbs))
            current_offset += count

        ubrs = []

        sos = SubOrder.objects.filter(product_id=product_id)
        sos.delete()

        for mobile, info in mobile_count_map.items():
            i = 1
            user = info['user']
            gbs = info['gbs']
            user_id = info['user_id']
            count = info['count']

            # 充值
            ubr_in = UserBalanceRecord(
                user=user, create_type=UserBalanceRecord.CreateType.RECHARGE.name, amount=info['total_value'],
                recharge_method=UserBalanceRecord.RechargeMethod.OFFLINE_FOR_DISPATCH.name,
                memo='产品分发')
            ubr_in.save()

            # 创建订单
            order_obj = Order(status='CREATED', no=Order.make_order_no(user.mobile),
                              create_type=Order.CreateType.PRE_SALE_BUY_IN.name,
                              amount=info['total_value'], buyer_user_id=user.id, expire_at=time.time() + 600)
            order_obj.expire_at = time.time() + 600
            order_obj.status = Order.Status.FINISHED.name
            order_obj.update_timestamp = time.time()
            order_obj.save()
            goods_repo = Repo.objects.get(pk=gbs[0].repo_id)

            size = 1000
            page = 1
            while True:
                while_start=time.time()
                unsaved_sos = []
                gb_ids = []
                page_gbs = gbs[size * (page - 1): size * page]
                print(mobile, count, 'current_page', len(page_gbs))
                if len(page_gbs) == 0:
                    break
                for good in page_gbs:  # 循环区块
                    in_for_start = time.time()
                    g_id = good.pk
                    repo_id = good.repo_id
                    gb_ids.append(g_id)
                    # 创建子订单
                    suborder = SubOrder(order_id=order_obj.id, goods_block_id=g_id, product_id=product_id,
                                        product_name=product.name, goods_block_repo_id=repo_id,
                                        goods_block_repo_name=goods_repo.name,
                                        store_unit_id=good.store_unit_id, price_per_100g_1d=Decimal('0.00'),
                                        amount=product.pre_sale_price, create_type="PRE_SALE_BUY_IN",
                                        repo_cost_start_datetime=datetime.datetime.now(),
                                        repo_cost_end_datetime=datetime.datetime.now(),
                                        goods_block_to_repo_id=goods_repo.id, goods_block_to_repo_name=goods_repo.name,
                                        )
                    unsaved_sos.append(suborder)
                    print('gb_index:', i)
                    i += 1
                # 区块超时时间修改
                GoodsBlock.objects.filter(id__in=gb_ids).update(locked_expire_at=time.time() + 900)
                SubOrder.objects.bulk_create(unsaved_sos)
                page += 1
                while_end = time.time()
                print('while',page, while_end-while_start)

            ubr_out = UserBalanceRecord(
                user_id=user_id, create_type=UserBalanceRecord.CreateType.BUY_PRE_SALE.name,
                amount=Decimal('-' + info['total_value'].__str__()),
                recharge_method=UserBalanceRecord.RechargeMethod.OFFLINE_FOR_DISPATCH.name,
                memo='产品分发支出')
            ubr_out.save()

            # # 预售购买成功消息
            msg = CreateMessageView.message(msg_type=Message.MsgType.PRE_SALE.name, oper_user=None,
                                            message_user_id=user_id, product_name=product.name,
                                            order_id=order_obj.id, market_order_id=None,
                                            title="购买成功：亲爱的囤囤用户，您选择的「{p_name}」{p_count}饼购买成功，共支付{o_amount}元，预售结果我们将在「消息通知」内即使通知您，请注意查收。".format(p_name=product.name, p_count=count, o_amount=info['total_value'].__str__()),
                                            content='购买成功：亲爱的囤囤用户，您选择的「{p_name}」{p_count}饼购买成功，共支付{o_amount}元，预售结果我们将在「消息通知」内即使通知您，请注意查收。'.format(p_name=product.name, p_count=count, o_amount=info['total_value'].__str__()))

            print('user: over: ', user.mobile)

        product.pre_sale_end_time = datetime.datetime.now() + datetime.timedelta(seconds=60 * 60)
        product.save()

    task_check_pre_sale_status(product_id)

    end = time.time()
    return '分发完成{ut}'.format(ut=end-start)
